use std::hint::black_box;

use criterion::Criterion;
use cs2s::{
    cache::{CacheTableClientHandle, CacheTableKeyRef, CacheTableServerHandle, CacheTableVal},
    error::Error,
    route::HandleRouter,
};
use cs2s_testdata::*;

pub fn enlargement(c: &mut Criterion) {
    const HOST_CODE: &[u8] = &[0xCC; 2048];
    c.bench_function("enlarge table", |b| {
        b.iter(|| {
            let mut handle = HandleRouter::new("bench-enlarge");

            handle.create(PATHS).unwrap();

            for i in 0..4096 {
                handle
                    .synchronize(
                        PATH_0,
                        &black_box(CacheTableKeyRef {
                            guest_addr: 0x1000 + i * 0x40,
                            guest_size: 1,
                            guest_sign: &[i as u8].into(),
                        }),
                        &black_box(CacheTableVal {
                            host_meta: &HOST_META_0,
                            host_code: HOST_CODE,
                        }),
                    )
                    .unwrap();
            }
        })
    });
}

pub fn lookup(c: &mut Criterion) {
    const NAME: &str = "bench-lookup";

    let mut table = CacheTableServerHandle::new(NAME, 1, 16).unwrap();
    let mut last = 0;

    for i in 0.. {
        last = i;
        let guest_addr = 0x400000 + i * 0x40;
        let guest_code = GUEST_CODES[i % GUEST_CODES.len()];
        let host_meta = HOST_METAS[i % HOST_METAS.len()];
        let host_code = HOST_CODES[i % HOST_CODES.len()];

        match table.insert(
            &CacheTableKeyRef {
                guest_addr,
                guest_size: guest_code.len(),
                guest_sign: &guest_code.into(),
            },
            &CacheTableVal {
                host_meta,
                host_code,
            },
        ) {
            Ok(_) => continue,
            Err(Error::MemNotEnough { .. }) => break,

            Err(err) => panic!("unexpected error: {:?}", err),
        }
    }
    last -= 1;

    let ro_table = CacheTableClientHandle::new(NAME).unwrap();

    c.bench_function("lookup the first", |b| {
        b.iter(|| {
            let i = black_box(0);
            let guest_addr = 0x400000 + i * 0x40;
            let guest_code = GUEST_CODES[i % GUEST_CODES.len()];
            let guest_sign = guest_code.into();
            let cache_key = CacheTableKeyRef {
                guest_addr,
                guest_size: guest_code.len(),
                guest_sign: &guest_sign,
            };

            let res = ro_table.lookup(&cache_key).unwrap();
            assert!(res.is_some());
            black_box(res);
        })
    });

    c.bench_function("lookup the last", |b| {
        b.iter(|| {
            let i = black_box(last);
            let guest_addr = 0x400000 + i * 0x40;
            let guest_code = GUEST_CODES[i % GUEST_CODES.len()];
            let guest_sign = guest_code.into();
            let cache_key = CacheTableKeyRef {
                guest_addr,
                guest_size: guest_code.len(),
                guest_sign: &guest_sign,
            };
            let res = ro_table.lookup(&cache_key).unwrap();
            assert!(res.is_some());
            black_box(res);
        })
    });
}
